package xj.graph2d;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

public class DrawShapeSearch {

  /*
   * Rules for order the matches 
   *   1. open shape < closed shape 
   *   2. corner < edge < inside
   */

  // search modes
  public static final int FIRST_MATCH = 0;

  public static final int BEST_MATCH = 1;

  public static final int GRAPH_NODE = 2;

  public static Result search(Iterator<DrawShape> shapes, Point2D p) {
    return search(shapes, p, BEST_MATCH);
  }

  public static Result search(Iterator<DrawShape> shapes, Point2D p, int mode) {
    Result result = null;
    if (shapes != null && p != null) {
      List<Result> matches = null;
      if (mode != FIRST_MATCH) {
	matches = new ArrayList<Result>(8);
      }

      boolean nodesOnly = (mode == GRAPH_NODE); 
      int position = DrawShapeConstants.PointOutside;
      while (shapes.hasNext()) {
	DrawShape shape = shapes.next();
	if (shape.isVisible() && 
	    shape.isSearchable() &&
	    (!nodesOnly || !(shape instanceof OpenShape))) {
	  position = shape.isInside(p);

	  if (debug) { 
	    System.out.println("DrawShapeSearch.search() position=" + 
			       Integer.toHexString(position));
	  }

	  if (position == DrawShapeConstants.PointOnMessage) {
	    return new Result(shape, position);
	  }

	  Result match = null;
	  if (position != DrawShapeConstants.PointOutside) {
	    if (!shape.isAtomic()) { 
		// && ((shape instanceof OpenShape) || isInterior(position))) {
	      // for a closed shape only search subshapes when in the interior of the shape
	      if (nodesOnly) { 
		match = shape.searchSubShapes(p, null, true);
	      } else { 
		match = shape.searchSubShapes(p);
	      }
	      if (match != null) {
		if (mode == FIRST_MATCH) {
		  return match;
		} else {
		  matches.add(match);
		}
	      }
	    }
	    if (match == null && 
		position != DrawShapeConstants.PointInBounds) {
	      match = new DrawShapeSearch.Result(shape, position);
	      if (mode == FIRST_MATCH) {
		return match;
	      } else {
		matches.add(match);
	      }
	    }
	  }
	}
      }

      // BEST_MATCH
      int n = matches.size();
      if (n > 0) {
	if (n > 1) {
	  Collections.sort(matches, comparator);
	}
	result = matches.get(0);
      }
    }
    return result;
  }

  // return a list of Results: all matches in descending order
  public static List<Result> searchAll(Iterator shapes, Point2D p) {
    return searchAll(shapes, p, null);
  }

  public static List<Result> searchAll(Iterator<DrawShape> shapes, 
				       Point2D p,
				       DrawShape exclude) {
    List<Result> matches = null;
    if (shapes != null && p != null) {
      int position = DrawShapeConstants.PointOutside;
      while (shapes.hasNext()) {
	DrawShape shape = shapes.next();
	if (shape.isVisible() && 
	    shape.isSearchable()) {
	  position = shape.isInside(p);
	  Result match = null;
	  if (position != DrawShapeConstants.PointOutside) {
	    if (!shape.isAtomic()) {
	      match = shape.searchSubShapes(p, exclude);
	      if (match != null) {
		if (matches == null) {
		  matches = new ArrayList<Result>(8);
		}
		matches.add(match);
	      }
	    }
	    if (match == null && position != DrawShapeConstants.PointInBounds) {
	      match = new DrawShapeSearch.Result(shape, position);
	      if (matches == null) {
		matches = new ArrayList<Result>(8);
	      }
	      matches.add(match);
	    }
	  }
	}
      }
      
      if (matches != null) {
	int n = matches.size();
	if (n > 1) {
	  Collections.sort(matches, comparator);
	}
      }
    }

    return matches;
  }

  // return true if pos is in the interior of a node
  public static boolean isInterior(int pos) { 
    if (pos == DrawShapeConstants.PointInside) {
      return true;
    } else { 
      int p1 = (pos & DrawShapeConstants.HIGH_MASK); 
      if (p1 == DrawShapeConstants.PointInHorizontalCompartment || 
	  p1 == DrawShapeConstants.PointInVerticalCompartment ||
	  p1 == DrawShapeConstants.PointOnCurve || 
	  p1 == DrawShapeConstants.PointOnPolyShapeEdge|| 
	  p1 == DrawShapeConstants.PointOnIcon|| 
	  p1 == DrawShapeConstants.PointOnMessage) { 
	return true; 
      }
    }
    return false; 
  }

  public static class Result {

    public Result(DrawShape shape, int position) {
      this.shape = shape;
      this.position = position;
    }

    public DrawShape shape;

    public int position;

  }

  static int getOrder(DrawShapeSearch.Result res) {
    int ord = Integer.MAX_VALUE;
    if (res != null) {
      DrawShape s = res.shape;
      int sord = 99;
      if (s != null) {
	int pos = (res.position & DrawShapeConstants.HIGH_MASK);
	int pord = 99;
	switch (pos) {
	case DrawShapeConstants.PointOnIcon:
	case DrawShapeConstants.PointOnMessage:
	  pord = 0;
	  break;

	case DrawShapeConstants.PointOnEnd:
	case DrawShapeConstants.PointOnCorner:
	case DrawShapeConstants.PointNECorner:
	case DrawShapeConstants.PointNWCorner:
	case DrawShapeConstants.PointSECorner:
	case DrawShapeConstants.PointSWCorner:
	case DrawShapeConstants.PointOnCurveEndPoint:
	case DrawShapeConstants.PointOnCurveCtrPoint:
	case DrawShapeConstants.PointOnPolyShapeVertex:
	case DrawShapeConstants.PointOnPolyCurveCtrPoint:
	case DrawShapeConstants.PointOnRotationHandle:
	  pord = 1;
	  break;

	case DrawShapeConstants.PointOnStringBaselineStart:
	case DrawShapeConstants.PointOnStringBaselineEnd:
	  pord = 3;
	  break;

	case DrawShapeConstants.PointOnEdge:
	case DrawShapeConstants.PointNorthEdge:
	case DrawShapeConstants.PointSouthEdge:
	case DrawShapeConstants.PointEastEdge:
	case DrawShapeConstants.PointWestEdge:
	case DrawShapeConstants.PointOnCircumference:
	case DrawShapeConstants.PointOnCurve:
	case DrawShapeConstants.PointOnPolyShapeEdge:
	case DrawShapeConstants.PointOnHorizontalDivider:
	case DrawShapeConstants.PointOnVerticalDivider:
	  pord = 5;
	  break;

	case DrawShapeConstants.PointInside:
	case DrawShapeConstants.PointInHorizontalCompartment:
	case DrawShapeConstants.PointInVerticalCompartment:
	  pord = 9;
	  break;
	}

	if (s instanceof OpenShape) {
	  if (s.isAcceptSubshape()) {
	    sord = 7;
	  } else {
	    sord = 1;
	  }
	} else if (s instanceof TextShape) {
	  sord = 3;
	  //pord = 1; 
	} else {
	  sord = 5;
	}

	// ord = sord * 100 + pord;
	ord = pord * 100 + sord;
      }
    }
    return ord;
  }

  static DrawShapeComparator comparator = new DrawShapeComparator();

  static class DrawShapeComparator implements Comparator<Result> {

    public int compare(Result res1, Result res2) {
      if (res1 != null && res2 != null) {
	int ord1 = getOrder(res1);
	int ord2 = getOrder(res2);
	if (ord1 != ord2) {
	  return (ord1 - ord2);
	} else {
	  DrawShape s1 = res1.shape;
	  DrawShape s2 = res2.shape;
	  if (s1 != null && s2 != null) {
	    Rectangle2D r1 = s1.getBounds();
	    Rectangle2D r2 = s2.getBounds();
	    if (r1.contains(r2)) {
	      return 1;
	    } else if (r2.contains(r1)) {
	      return -1;
	    } else {
	      float w1 = (float) r1.getWidth();
	      float h1 = (float) r1.getHeight();
	      float w2 = (float) r2.getWidth();
	      float h2 = (float) r2.getHeight();
	      if (w1 > w2 && h1 > h2) {
		return 1;
	      } else if (w2 > w1 && h2 > h1) {
		return -1;
	      }
	      if (w1 * h1 > w2 * h2) {
		return 1;
	      } else if (w2 * h2 > w1 * h1) {
		return -1;
	      }
	    }
	  }
	}
      }
      return 0;
    }

  }

  protected static final boolean debug = false;

}
